home *** CD-ROM | disk | FTP | other *** search
/ Delphi Magazine Collection 2001 / Delphi Magazine Collection 20001 (2001).iso / DISKS / Issue31 / hash2 / HashLinP.pas next >
Encoding:
Pascal/Delphi Source File  |  1997-11-29  |  14.7 KB  |  471 lines

  1. {*********************************************************}
  2. {* HashLinP                                              *}
  3. {* Copyright (c) Julian M Bucknall 1997                  *}
  4. {* All rights reserved.                                  *}
  5. {*********************************************************}
  6. {* Dynamic Hash Table using Linear Probing               *}
  7. {*********************************************************}
  8.  
  9. {Note: this unit is released as freeware. In other words, you are free
  10.        to use this unit in your own applications, however I retain all
  11.        copyright to the code. JMB}
  12.  
  13. unit HashLinP;
  14.  
  15. interface
  16.  
  17. {$IFOPT D+}
  18. {$DEFINE DebugMode}
  19. {$ENDIF}
  20.  
  21. type
  22.   ThtHashFunction = function (const S : string) : longint;
  23.     {-Function type for a hash function}
  24.   ThtDeleteString = procedure (const S : string; aObject : pointer);
  25.     {-Procedural type for a routine to free an associated object when
  26.       a hash element (ie, string) is deleted from the table}
  27.  
  28. type
  29.   ThtHashTableLinear = class
  30.     {-a hash table that uses linear probing to resolve collisions}
  31.     private
  32.       htlArray     : pointer;
  33.       htlCount     : integer;
  34.       htlDeleteStr : ThtDeleteString;
  35.       htlHashFunc  : ThtHashFunction;
  36.       htlTableSize : integer;
  37.       {$IFDEF DebugMode}
  38.       htlDebugSeeks: integer;
  39.       {$ENDIF}
  40.     protected
  41.       procedure htlAlterTableSize(aNewTableSize : integer);
  42.       procedure htlDoDeleteString(const aKey : string; aObject : pointer);
  43.       function htlFindPrim(const aKey : string; var aIndex : integer) : boolean;
  44.       procedure htlGrowTable;
  45.       function htlHash(const aKey : string) : integer;
  46.       procedure htlShrinkTable;
  47.  
  48.     public
  49.       constructor Create(aTableSize : integer;
  50.                          aHashFunc  : ThtHashFunction);
  51.         {-constructor to create a hash table that can hold aTableSize
  52.           elements and that uses aHashFunc to hash strings}
  53.       destructor Destroy; override;
  54.         {-destructor to destroy the hash table}
  55.  
  56.       procedure Delete(const aKey : string);
  57.         {-delete the element defined by aKey; an exception is raised
  58.           if the string is not found}
  59.       procedure Empty;
  60.         {-delete all elements in the hash table and reset it to empty}
  61.       function Find(const aKey : string; var aObject : pointer) : boolean;
  62.         {-find the element defined by aKey; return true and the
  63.           associated object if the string is found, otherwise false}
  64.       procedure Insert(const aKey : string; aObject : pointer);
  65.         {-insert a new element defined by aKey with its associated
  66.           object aObject; an exception is raised if the string is
  67.           already present}
  68.  
  69.       property Count : integer read htlCount;
  70.         {-current number of elements in the hash table}
  71.       property OnDeleteString : ThtDeleteString
  72.          read htlDeleteStr write htlDeleteStr;
  73.         {-routine to delete an associated object when the string is
  74.           deleted}
  75.  
  76.  
  77.       {$IFDEF DebugMode}
  78.       procedure debugPrint(aFileName : string; aDetailed : boolean);
  79.       {$ENDIF}
  80.   end;
  81.  
  82. implementation
  83.  
  84. uses
  85.   SysUtils;
  86.  
  87. type
  88.   THashElementState = (hesEmpty, hesDeleted, hesInUse);
  89.  
  90.   THashElement = packed record
  91.     {$IFDEF Windows}
  92.     heString : PString;
  93.     {$ELSE}
  94.     heString : string;
  95.     {$ENDIF}
  96.     heObject : pointer;
  97.     heState  : THashElementState;
  98.     heFiller : array [0..2] of byte;
  99.   end;
  100.  
  101.   PHashElementArray = ^THashElementArray;
  102.   THashElementArray =
  103.      array [0..pred(MaxInt div sizeof(THashElement))] of THashElement;
  104.  
  105.  
  106. {===Helper routines==================================================}
  107. procedure RaiseException(const S : string);
  108. begin
  109.   raise Exception.Create(S);
  110. end;
  111. {--------}
  112. function GetClosestPrime(N : integer) : integer;
  113. {$I Primes.inc}
  114. const
  115.   Forever = true;
  116. var
  117.   L, R, M : integer;
  118.   RootN   : integer;
  119.   IsPrime : boolean;
  120.   DivisorIndex : integer;
  121. begin
  122.   {treat 2 as a special case}
  123.   if (N = 2) then begin
  124.     Result := N;
  125.     Exit;
  126.   end;
  127.   {make the result equal to N, and if it's even, the next odd number}
  128.   if Odd(N) then
  129.     Result := N
  130.   else
  131.     Result := succ(N);
  132.   {if the result is within our prime number table, use binary search
  133.    to find the equal or next highest prime number}
  134.   if (Result <= MaxPrime) then begin
  135.     L := 0;
  136.     R := pred(PrimeCount);
  137.     while (L <= R) do begin
  138.       M := (L + R) div 2;
  139.       if (Result = Primes[M]) then
  140.         Exit
  141.       else if (Result < Primes[M]) then
  142.         R := pred(M)
  143.       else
  144.         L := succ(M);
  145.     end;
  146.     Result := Primes[L];
  147.     Exit;
  148.   end;
  149.   {the result is outside our prime number table range, use the
  150.    standard method for testing primality (do any of the primes up to
  151.    the root of the number divide it exactly?) and continue
  152.    incrementing the result by 2 until it is prime}
  153.   if (Result <= (MaxPrime * MaxPrime)) then begin
  154.     while Forever do begin
  155.       RootN := round(Sqrt(Result));
  156.       DivisorIndex := 1; {ignore the prime number 2}
  157.       IsPrime := true;
  158.       while (DivisorIndex < PrimeCount) and (RootN > Primes[DivisorIndex]) do begin
  159.         if ((Result div Primes[DivisorIndex]) * Primes[DivisorIndex] = Result) then begin
  160.           IsPrime := false;
  161.           Break;
  162.         end;
  163.         inc(DivisorIndex);
  164.       end;
  165.       if IsPrime then
  166.         Exit;
  167.       inc(Result, 2);
  168.     end;
  169.   end;
  170. end;
  171. {====================================================================}
  172.  
  173.  
  174. {===ThtHashTableLinear===============================================}
  175. constructor ThtHashTableLinear.Create(aTableSize : integer;
  176.                                       aHashFunc  : ThtHashFunction);
  177. begin
  178.   inherited Create;
  179.   aTableSize := GetClosestPrime(aTableSize);
  180.   GetMem(htlArray, aTableSize * sizeof(THashElement));
  181.   FillChar(htlArray^, aTableSize * sizeof(THashElement), 0);
  182.   htlTableSize := aTableSize;
  183.   htlHashFunc := aHashFunc;
  184. end;
  185. {--------}
  186. destructor ThtHashTableLinear.Destroy;
  187. begin
  188.   if (htlArray <> nil) then begin
  189.     Empty;
  190.     FreeMem(htlArray, htlTableSize * sizeof(THashElement));
  191.   end;
  192.   inherited Destroy;
  193. end;
  194. {--------}
  195. {$IFDEF DebugMode}
  196. procedure ThtHashTableLinear.debugPrint(aFileName : string; aDetailed : boolean);
  197. const
  198.   StateStrs : array [THashElementState] of string[9] =
  199.               ('<empty>  ', '<deleted>', '<in use> ');
  200. var
  201.   Inx        : integer;
  202.   discardInx : integer;
  203.   TotSeeks   : integer;
  204.   F          : System.Text;
  205. begin
  206.   System.Assign(F, aFileName);
  207.   System.Rewrite(F);
  208.   try
  209.     writeln(F, 'Hash Table (Linear Probe) Debug Print [', aFileName, ']');
  210.     writeln(F, '-------------------------------------');
  211.     if aDetailed then
  212.       writeln(F);
  213.     TotSeeks := 0;
  214.     for Inx := 0 to pred(htlTableSize) do begin
  215.       with PHashElementArray(htlArray)^[Inx] do begin
  216.         if aDetailed then
  217.           write(F, Inx:4, ': ', StateStrs[heState]);
  218.         if (heState = hesInUse) then begin
  219.           {$IFDEF Windows}
  220.           htlFindPrim(heString^, discardInx);
  221.           {$ELSE}
  222.           htlFindPrim(heString, discardInx);
  223.           {$ENDIF}
  224.           inc(TotSeeks, htlDebugSeeks);
  225.           if aDetailed then
  226.             {$IFDEF Windows}
  227.             writeln(F, '  ', heString^, '  (seekcount: ', htlDebugSeeks, ')')
  228.             {$ELSE}
  229.             writeln(F, '  ', heString, '  (seekcount: ', htlDebugSeeks, ')')
  230.             {$ENDIF}
  231.         end
  232.         else
  233.           if aDetailed then
  234.             writeln(F);
  235.       end;
  236.     end;
  237.     writeln(F);
  238.     writeln(F, 'The table has ', htlCount,
  239.                ' element(s) (of ', htlTableSize,
  240.                ') and is ', (100.0 * htlCount / htlTableSize):0:2,
  241.                '% full');
  242.     if (htlCount > 0) then
  243.       writeln(F, 'The average path length is ', (TotSeeks / htlCount):0:2, ' seeks');
  244.   finally
  245.     System.Close(F);
  246.   end;
  247. end;
  248. {$ENDIF}
  249. {--------}
  250. procedure ThtHashTableLinear.Delete(const aKey : string);
  251. var
  252.   Inx : integer;
  253. begin
  254.   if not htlFindPrim(aKey, Inx) then
  255.     RaiseException('ThtHashTableLinear.Delete: key not found');
  256.   with PHashElementArray(htlArray)^[Inx] do begin
  257.     {$IFDEF Windows}
  258.     htlDoDeleteString(heString^, heObject);
  259.     DisposeStr(heString);
  260.     {$ELSE}
  261.     htlDoDeleteString(heString, heObject);
  262.     heString := '';
  263.     {$ENDIF}
  264.     heState := hesDeleted;
  265.   end;
  266.   dec(htlCount);
  267.   {shrink the table if we're under 1/6 full}
  268.   if ((htlCount * 6) < htlTableSize) then
  269.     htlShrinkTable;
  270. end;
  271. {--------}
  272. procedure ThtHashTableLinear.Empty;
  273. var
  274.   Inx : integer;
  275. begin
  276.   for Inx := 0 to pred(htlTableSize) do begin
  277.     with PHashElementArray(htlArray)^[Inx] do begin
  278.       if (heState = hesInUse) then begin
  279.         {$IFDEF Windows}
  280.         htlDoDeleteString(heString^, heObject);
  281.         DisposeStr(heString);
  282.         {$ELSE}
  283.         htlDoDeleteString(heString, heObject);
  284.         heString := '';
  285.         {$ENDIF}
  286.       end;
  287.       heState := hesEmpty;
  288.     end;
  289.   end;
  290.   htlCount := 0;
  291. end;
  292. {--------}
  293. function ThtHashTableLinear.Find(const aKey : string; var aObject : pointer) : boolean;
  294. var
  295.   Inx : integer;
  296. begin
  297.   if htlFindPrim(aKey, Inx) then begin
  298.     Result := true;
  299.     aObject := PHashElementArray(htlArray)^[Inx].heObject;
  300.   end
  301.   else begin
  302.     Result := false;
  303.     aObject := nil;
  304.   end;
  305. end;
  306. {--------}
  307. procedure ThtHashTableLinear.htlAlterTableSize(aNewTableSize : integer);
  308. var
  309.   Inx          : integer;
  310.   OldTableSize : integer;
  311.   NewArray     : PHashElementArray;
  312.   OldArray     : PHashElementArray;
  313. begin
  314.   {allocate a new array}
  315.   GetMem(NewArray, aNewTableSize * sizeof(THashElement));
  316.   FillChar(NewArray^, aNewTableSize * sizeof(THashElement), 0);
  317.   {save the old array and element count and then set the object
  318.    fields to the new values}
  319.   OldArray := PHashElementArray(htlArray);
  320.   OldTableSize := htlTableSize;
  321.   htlArray := NewArray;
  322.   htlTableSize := aNewTableSize;
  323.   htlCount := 0;
  324.   {read through the old array and transfer over the strings/objects}
  325.   for Inx := 0 to pred(OldTableSize) do begin
  326.     with OldArray^[Inx] do begin
  327.       if (heState = hesInUse) then begin
  328.         {$IFDEF Windows}
  329.         Insert(heString^, heObject);
  330.         DisposeStr(heString);
  331.         {$ELSE}
  332.         Insert(heString, heObject);
  333.         heString := '';
  334.         {$ENDIF}
  335.       end;
  336.     end;
  337.   end;
  338.   {finally free the old array}
  339.   FreeMem(OldArray, OldTableSize * sizeof(THashElement));
  340. end;
  341. {--------}
  342. procedure ThtHashTableLinear.htlDoDeleteString(const aKey : string; aObject : pointer);
  343. begin
  344.   if Assigned(htlDeleteStr) then
  345.     htlDeleteStr(aKey, aObject);
  346. end;
  347. {--------}
  348. function ThtHashTableLinear.htlFindPrim(const aKey : string; var aIndex : integer) : boolean;
  349. var
  350.   FirstDeleted : integer;
  351.   KeyHash      : integer;
  352.   FirstKeyHash : integer;
  353. begin
  354.   {assume we'll fail}
  355.   Result := false;
  356.   {we may need to make note of the first deleted element we find, so
  357.    set the variable to some impossible value so that we know whether
  358.    we found one yet}
  359.   FirstDeleted := -1;
  360.   {calculate the hash for the string, make a note of it so we can find
  361.    out when (if) we wrap around the table completely}
  362.   KeyHash := htlHash(aKey);
  363.   FirstKeyHash := KeyHash;
  364.   {$IFDEF DebugMode}
  365.   htlDebugSeeks := 1;
  366.   {$ENDIF}
  367.   {do forever - we'll be exiting out of the loop when needed}
  368.   while true do begin
  369.     {with the current element...}
  370.     with PHashElementArray(htlArray)^[KeyHash] do
  371.       case heState of
  372.         hesEmpty   : begin
  373.                        {the state is 'empty', we must stop the linear
  374.                         probe and return either this index or the
  375.                         first deleted one we encountered}
  376.                        if (FirstDeleted <> -1) then
  377.                          aIndex := FirstDeleted
  378.                        else
  379.                          aIndex := KeyHash;
  380.                        Exit;
  381.                      end;
  382.         hesDeleted : begin
  383.                        {the state is 'deleted', we must make a note of
  384.                         this index if it's the first one we found and
  385.                         continue the linear probe}
  386.                        if (FirstDeleted = -1) then
  387.                          FirstDeleted := KeyHash;
  388.                      end;
  389.         hesInUse   : begin
  390.                        {the state is 'in use', we check to see if it's
  391.                         our string, if it is, exit returning true and
  392.                         the index}
  393.                        {$IFDEF Windows}
  394.                        if (heString^ = aKey) then begin
  395.                        {$ELSE}
  396.                        if (heString = aKey) then begin
  397.                        {$ENDIF}
  398.                          aIndex := KeyHash;
  399.                          Result := true;
  400.                          Exit;
  401.                        end;
  402.                      end;
  403.       else
  404.         {bad news}
  405.         RaiseException('ThtHashTableLinear.htlFindPrim: invalid element state')
  406.       end;{case}
  407.     {we didn't find the key or an empty slot this time around, so
  408.      increment the index (taking care of the wraparound) and exit if
  409.      we've got back to the start again}
  410.     inc(KeyHash);
  411.     if (KeyHash = htlTableSize) then
  412.       KeyHash := 0;
  413.     if (KeyHash = FirstKeyHash) then begin
  414.       if (FirstDeleted <> -1) then
  415.         aIndex := FirstDeleted
  416.       else
  417.         aIndex := -1; {this value means that the table is full}
  418.       Exit;
  419.     end;
  420.     {$IFDEF DebugMode}
  421.     inc(htlDebugSeeks);
  422.     {$ENDIF}
  423.   end;{forever loop}
  424. end;
  425. {--------}
  426. procedure ThtHashTableLinear.htlGrowTable;
  427. begin
  428.   {make the table roughly twice as large as before}
  429.   htlAlterTableSize(GetClosestPrime(succ(htlTableSize * 2)));
  430. end;
  431. {--------}
  432. function ThtHashTableLinear.htlHash(const aKey : string) : integer;
  433. begin
  434.   if Assigned(htlHashFunc) then
  435.     Result := htlHashFunc(aKey) mod htlTableSize
  436.   else
  437.     Result := 0;
  438. end;
  439. {--------}
  440. procedure ThtHashTableLinear.htlShrinkTable;
  441. begin
  442.   {make the table roughly half as large as before}
  443.   htlAlterTableSize(GetClosestPrime(pred(htlTableSize) div 2));
  444. end;
  445. {--------}
  446. procedure ThtHashTableLinear.Insert(const aKey : string; aObject : pointer);
  447. var
  448.   Inx : integer;
  449. begin
  450.   if htlFindPrim(aKey, Inx) then
  451.     RaiseException('ThtHashTableLinear.Insert: duplicate key');
  452.   if (Inx = -1) then
  453.     RaiseException('ThtHashTableLinear.Insert: table is full');
  454.   with PHashElementArray(htlArray)^[Inx] do begin
  455.     {$IFDEF Windows}
  456.     heString := NewStr(aKey);
  457.     {$ELSE}
  458.     heString := aKey;
  459.     {$ENDIF}
  460.     heObject := aObject;
  461.     heState := hesInUse;
  462.   end;
  463.   inc(htlCount);
  464.   {grow the table if we're over 2/3 full}
  465.   if ((htlCount * 3) > (htlTableSize * 2)) then
  466.     htlGrowTable;
  467. end;
  468. {====================================================================}
  469.  
  470. end.
  471.